package sts_mgr

import (
	"code.oldboyedu.com/studygo/day04/stsMgr_package/student"
	"encoding/json"
	"fmt"
	log "github.com/sirupsen/logrus"
	"io/ioutil"
)

// StuMgr 定义一个学生管理者结构体，用来保存学生数据
type StuMgr struct {
	allSts   []*student.Students       //用来保存学生数据
	dataFile string                    //定义存储学生的数据的文件
	sysById  map[int]*student.Students //定义存储学生id的变量 因为学生结构体中定义了一个类型未int的Id 主要用来存储它
}

// NewStuMgr 构造学生管理器的结构体函数
func NewStuMgr(dataFile string) *StuMgr {
	return &StuMgr{
		allSts:   make([]*student.Students, 0),
		dataFile: dataFile,
		sysById:  make(map[int]*student.Students, 0),
	}
}

/*
	定义保存数据文件的函数 采用ioutil.Writer函数
	当我们调用 ioutil.WriteFile() 函数时，它会首先尝试以指定的文件名创建一个新的文件，然后将数据写入到该文件中。
	如果该文件已经存在，则会先清空该文件中的内容，然后将数据写入文件中。

	如果您需要保留之前的数据并在其基础上添加新的数据，您可以使用以下方法：
		1、在保存数据之前，先读取文件中已有的json数组并反序列化为数据结构；
		2、将新的数据追加到旧数据后面；
		3、关闭文件；
		4、重新以写入模式打开同一个文件，这将清空文件的内容；
		5、将整个数据结构序列化为json数据；
		6、写入json数据并关闭文件。
		这样做可以保留之前保存的数据，并且能够正确地将新的数据追加到文件中。
        [{"f":{"id":1,"name":"f","age":0,"class":["w","exir"]}},{"d":{"id":4,"name":"d","age":0,"class":["q","yw"]}}]
*/
// 将学生信息保存到文件中
func (s *StuMgr) SaveFile() {
	stsdata, _ := json.Marshal(s.allSts) //在此之前如果已经执行过增、删、改操作,则执行保存时相当于stsdata中保存的已经是执行函数后的最新数据
	err := ioutil.WriteFile(s.dataFile, stsdata, 0666)
	if err != nil {
		log.Error("保存数据失败", err)
		return
	} //在调用ioutil.WriteFile时,相当于打开文件并清空文件，将最新的数据stsdata重新写进去
	log.Info("保存数据成功")
	return
}

/*
	定义读文件函数  ioutil.ReadFile
	1、先读取文件,如果读取失败,则返回nil
	2、创建一个类型为[]*Students的data变量,用于存储反序列后的数据
	3、循环这个反序列化后的数据,将这个数据追加到保存总的数据变量中即s.allSts中，再将id保存到sysById中
		因为定义了一个map类型的sysById，用于存储信息id
*/
func (s *StuMgr) LoadFile() []*student.Students {
	file, err := ioutil.ReadFile(s.dataFile)
	if err != nil {
		log.Error("打开数据文件失败", err)
		return nil
	}
	var data []*student.Students
	err = json.Unmarshal(file, &data)
	if err != nil {
		log.Error("json序列化失败", err)
		return nil
	}
	for _, sts := range data {
		s.allSts = append(s.allSts, sts)
		s.sysById[sts.Id] = sts
	}
	log.Info("读数据文件成功,数据是 ", data)
	return data
}

// Viewallsts 查看全部学生信息
/*
	因为学生信息都保存在学生管理器的结构体中,想要查看学生信息需要一个接收者,这个接收者s *StuMgr返回的是一个经过make初始化的存储空间
	因为是一个key为string类型、value为*Student的切片类型,因此查看学生信息可以通过 切片类型的for循环拿到对应的value，这个value就是map类型
然后这个循环这个value,打印出values为*Student保存的值
*/
func (s *StuMgr) Viewallsts() {
	for _, value := range s.allSts {
		log.Info("全部学生信息: ", value.Id, value.Name, value.Age, value.Class)
		fmt.Println(value.Id, value.Name, value.Age, value.Class)
	}
}

// Viewoncests 查看单个学生信息
/*
	因为学生信息都保存在学生管理器的结构体中,想要查看学生信息需要一个接收者,这个接收者s *StuMgr返回的是一个经过make初始化的存储空间
该函数接收一个传递进来的int参数,通过输入的id与值为*Student类型中的id对比,如果相等,则打印出这个id对应的用户信息
	接收者表示的是调用该方法的具体类型变量
*/
func (s *StuMgr) Viewoncests(id int) bool {
	for key, _ := range s.sysById {
		if key == id {
			fmt.Println("单个学生信息: ", s.sysById[key].Id, s.sysById[key].Name, s.sysById[key].Age, s.sysById[key].Class)
			log.Info("单个学生信息: ", s.sysById[key].Id, s.sysById[key].Name, s.sysById[key].Age, s.sysById[key].Class)
			break
		}
	}
	log.Info("查看单个学生信息成功")
	return true
}

// AddMsgsts  增加学生信息
/*
	该函数接收一个*Students类型的参数 最后把增加的信息通过接收者s *StuMgr 保存到StuMgr结构体中
先是循环切片,拿个每个索引对应的值，该值是map类型。然后在通过map的ok判断方法，检查存储空间中是否有已经存在的键所对应的值，如果有则直接eturn false
如果存储空间中没有，则使用make初始化一个map类型，将传进来的形参保存到这个map类型中,再将这个map通过切片的append方法追加到存储空间中,实现增加功能
*/
func (s *StuMgr) AddMsgsts(stuMgs *student.Students) bool {
	if stuMgs == nil || stuMgs.Id == 0 || stuMgs.Name == "" || stuMgs.Age == 0 || stuMgs.Class == nil {
		log.Error("传参判断失败,请检查输入的参数是否合法~~")
		return false
	}

	//判断一切合法后,将数据追加到s.allSts中,并将信息中的id号记录到sysById中,保存
	s.allSts = append(s.allSts, stuMgs)
	log.Info("新增的学生信息成功追加到s.allSts")
	s.sysById[stuMgs.Id] = stuMgs
	log.Info("新增学生的id已保存到s.sysById")
	s.SaveFile()
	return true
}

// UpdateMsgsts 更新函数
/*
	通过传递的形参id,判断想要更新的信息是否存在,先是循环切片，拿到map 再通过循环map，进行传入的id与保存的id进行对比，如果相等,则调用另一个形参student
进行map类型的增加操作，将旧值修改为新值，然后再调用写文件操作，返回一个true
*/
func (s *StuMgr) UpdateMsgsts(id int, stuMgs *student.Students) bool {
	// 判断传入的学生信息是否有效
	if stuMgs == nil || stuMgs.Id == 0 || stuMgs.Name == "" || stuMgs.Age == 0 || stuMgs.Class == nil {
		log.Error("传入的参数不合法或不符合类型~~~")
		return false
	} else {
		// 更新学生信息 因为sts的类型是*Students
		sts, _ := s.sysById[id]
		sts.Id = stuMgs.Id
		sts.Name = stuMgs.Name
		sts.Age = stuMgs.Age
		sts.Class = stuMgs.Class
		s.SaveFile()
		log.Info("修改学生信息成功")
		return true
	}
}

// DelMsgsts 删除学生信息
/*
	通过传入的形参id判断对应的信息是否存在，先进行切片循环,拿到对应的map 在进行map循环操作,判断*Student值中是否有与形参id一样的值，如果有，则进行
删除map操作，删除后,再进行判断map的长度是否为0了，如果确实是0，则进行切片的删除操作，将切片中的索引进行删除即可
*/
func (s *StuMgr) DelMsgsts(id int) bool {
	// 判断传入的学生信息是否有效
	//if id < 0 && id > len(s.allSts) {
	//	log.Error("传参id未在范围内", id)
	//	return false
	//}
	//_, exist := s.sysById[id]
	//if !exist {
	//	log.Error("传参id未在id存储器中找到")
	//	return false
	//} else {
	for index, value := range s.allSts {
		if value.Id == id {
			s.allSts = append(s.allSts[:index], s.allSts[index+1:]...)
			break
		}
	}
	delete(s.sysById, id)
	s.SaveFile()
	log.Info("删除学生信息成功")
	return true
}
func (s *StuMgr) Modtify(id int) bool {
	//通过mpa类型中判断值是否存在的方法，判断输入的id是否在保存id的变量中存在
	_, exist := s.sysById[id]
	if !exist {
		log.Error("id存储器中不存在传入的id: ", id)
		return false
	}
	return true
}
